#!/usr/bin/env python
#-*- encoding:utf-8 -*-
import conf
import urllib2,urllib
import httplib
import urlparse
import time
import threading
import os
import traceback
import MySQLdb
import logging
import socket
import re
import BaseHTTPServer
socket.setdefaulttimeout(30)


#################global ####################
g_manage = ""
g_dbw = ""
g_dbr = ""

#################logging ###################
logging.basicConfig(level=logging.DEBUG,
                format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                datefmt='%a, %d %b %Y %H:%M:%S',
                filename='./monitor.log',
                filemode='w')

def getUrlData(url):
    data = ""
    try:
        fd = urllib2.urlopen(url)
        data = fd.read()
        code = 200
        fd.close()
    except:
        code = False    
    return {"data": data, 'code': code}
 
def detectHttp(url):
    try:
        (proto, host, path, x1, param, x2) = urlparse.urlparse(url)
        conn = httplib.HTTPConnection(host)
        if param:
            conn.request("GET","%s?%s" % (path, param))
        else:
            conn.request("GET", path)
        res = conn.getresponse()
        conn.close()
        if res.status == 200:
            return True
        else:
            print "http error: ", res.status, "url:", url
            return False
    except:
        print "http error"
        return False

def detectPort(host, port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        s.connect((str(host), int(port)))
        s.send("monitor test")
        s.close()
        return True
    except socket.error, msg:
        return False
 
def idExist(id):
    flag = False
    sql = "select id from monitor_monitor where id=%d and open='1'" % int(id)
    ret = g_dbr.executeSqlRet(sql)
    if ret:
        flag = True
    return flag

def url2str(url):
    return "[%s]" % url['mname']

def sendMail(msg, mail):
    msg = "%s" % msg
    cmd = u"pysend -s  '%s' -t %s -e '%s'" %(msg, mail, msg)
    print cmd.encode('utf-8')
    return os.system(cmd.encode('utf-8'))

def sendMobile(msg, mobile):
    msg = "%s" % msg
    cmd = '''curl 'http://125.39.0.45/sms.php?p=%s&m="%s"' ''' % (mobile, urllib.quote(msg.encode('utf-8')))
    print cmd
    return os.system(cmd)

def sendGtalk(msg, gtalk):
    msg = "%s" % msg
    cmd = u"gsend -t %s -m '%s'" %(gtalk, msg)
    print cmd.encode('utf-8')
    return os.system(cmd.encode('utf-8'))

def sendMsn(msg, msn):
    return True

class DbObject:
    #数据库是共享互斥异步的
    def __init__(self, host, user, passwd, database, port=3306, charset="utf8", dict=False):
        self._conn = MySQLdb.connect(host=host, user=user, passwd=passwd, \
                        db=database, port=port, charset=charset)
        if dict:
            self._cursor = self._conn.cursor(cursorclass = MySQLdb.cursors.DictCursor)
        else:
            self._cursor = self._conn.cursor()
        self._cursor.execute("SET NAMES 'utf8'")
        self._l = threading.Lock()

    def executeSql(self ,sql):
        self._l.acquire()
        try:
            sql = sql.encode("utf-8")
            res = self._cursor.execute(sql)
            logging.info("execute: %s, status: %s" % (sql, res))
        except:
            traceback.print_exc()
            res = False
        #finally:
        self._l.release()
        return res
     
    def executeSqlRet(self ,sql):
        self._l.acquire()
        try:
            sql = sql.encode("utf-8")
            stat = self._cursor.execute(sql)
            logging.info("execute: %s, status: %s" % (sql, stat))
            res = self._cursor.fetchall()
        except:
            traceback.print_exc()
            res = False
        #finally:
        self._l.release()
        return res
     
    def __del__(self):
        self._cursor.close()


class Manage:
    
    def __init__(self):
        #data用来记录全局数据，记录每个进程的状态
        #0：停止状态
        #1:运行状态
        #2：刷新状态
        #3：强制退出状态
        self._data = {}
        self._lock = threading.Lock()
        
    def status(self):
        info = ""
        for k, v in self._data.items():
            info += "ID:%s , Status:%d \n" % (k, v)
        info += "Total %d Threads\n" % len(self._data)
        return info
        
    def register(self, id):
        self._lock.acquire()
        status = self._data.get(str(id), 0)
        if status != 0:
            ret = False
            logging.info("Thread Resiger Fail, ID: %d" % id)
        else:
            self._data[str(id)] = 1
            logging.info("Thread Resiger OK, ID: %d" % id)
            ret = True  
        self._lock.release()
        return ret
        
    def unregister(self, id):
        logging.info("Thread Unresiger OK, ID: %d" % id)
        return self._setValue(id, 0)
    
    def isExist(self, id):
        ret = self._checkValue(id, 0)
        if ret == True:
            return False
        else:
            return True

    def isRefresh(self, id):
        return self._checkValue(id, 2)
  
    def setRefresh(self, id):
        return self._setValue(id, 2)
    
    def doneRefresh(self, id):
        return self._setValue(id, 1)
        
    def isExit(self, id):
        return self._checkValue(id, 3)
        
    def setExit(self, id):
        return self._setValue(id, 3)
 
    def resume(self, id):
        return self._setValue(id, 1)
       
    def _checkValue(self, id ,value):
        status = self._data.get(str(id), 0)
        if status == value:
            ret = True
        else:
            ret = False
        return ret
 
    def _setValue(self, id, value):
        self._lock.acquire()
        status = self._data.get(str(id), 0)
        if status == 0:
            ret = False
        else:
            self._data[str(id)] = value
            ret = True
        self._lock.release()
        return ret


#收集数据，并且过滤处理，然后发送数据库
class Collecter(threading.Thread):
    name = "Collecter"
    
    def __init__(self, db, dbr, id, manage):
        print "Start %s, ID: %d..." % (self.name, id)
        threading.Thread.__init__(self)
        self._db = db
        self._dbr = dbr
        self._id = id
        self._manage = manage
        #向行程管理者注册
        self._register = self._manage.register(id)
        self._url = self.loadConfig(id, dbr)
        if not self._url.has_key('data'):
            self._url['data'] = {"total": 0, "now": 0, "status": True}
        #启动线程
        self.setDaemon(1)
        self.setName(str(id))
        self.start()
        logging.info("Start %s, ID: %d..." % (self.name, id))
        
    
    def loadConfig(self, id, db):
        url = {}
        #获取监控参数
        sql = "select * from monitor_monitor where id=%d" % id
        ret = db.executeSqlRet(sql)
        if ret:
            url = ret[0]
            
        #获取报警参数
        sql = """SELECT mail, gtalk, mobile, msn, mode FROM monitor_alertreceive AS recv LEFT JOIN 
               users_userprofile AS prof ON recv.uid_id = prof.user_id  WHERE mid_id=%d"""  % id
        people = db.executeSqlRet(sql)
        if len(people) > 0:
            url['people'] = people
        else:
            url['people'] = {}
        return url
        
    def run(self):
        #注册不成功，立刻返回
        if self._register != True:
            logging.error("start thread error, id %d" % self._id)
            return False
        
        #监控关闭，立刻返回
        if self._url.get('open', '0') == '0':
            logging.info("[url: %s stop, status: %s]" % (self._url.get('url', 'NULL'), self._url.get('status', 'False')))
            return True
        
        interval = self._url['freq']
        tpl = Tpl(self._url)
        alt = Alerter(self._dbr, self._url)
        se = Sender(self._db, self._url)
    
        while True:
            #是否有退出信号
            if self._manage.isExit(self._id) == True:
                logging.info("Thread exit, because receive status:3")
                print "Thread exit, because receive status:3"
                break
            if self._manage.isRefresh(self._id) == True:
                self._url = self.loadConfig(id, self._dbr)
                self._url['data']  = {}
                logging.info("Thread  refresh, because recive status:2")
                self._manage.doneRefresh(self._id)
                tpl = Tpl(self._url)
                alt = Alerter(self._dbr, self._url)
                se = Sender(self._db, self._url)
                interval = self._url['freq']
                
                
            tpl.process()
            alt.check()
            se.process()
            time.sleep(interval)

        #向线程管理者销毁注册
        print "Thread unregister on __del__, ID: %d" % self._id
        self._manage.unregister(self._id)
            
    def __del__(self):
        #向线程管理者销毁注册
        print "Thread unregister on __del__, ID: %d" % self._id
        self._manage.unregister(self._id)

class Alerter:
    name = "Alerter"    

    def __init__(self, db, url):
        print "Start %s..." % self.name
        self._url = url
        self._db = db
        #实际错误次数
        if not url.has_key('alert_num'):
            url['alert_num'] = 0
     
        if not url.has_key('real_alert_num'):
            url['real_alert_num'] = 0
            
            
    def _saveAlert(self):
        url = self._url
        flag = 1
        if url['data']['status'] == False:
            flag = 0
            
        downtime = 0
        if flag == 1:
            now = time.time()
            downtime = now - url.get('error_time', now)
         
        sql = """insert into monitor_alert (mid_id,type,timestamp,downtime, reason) values (%d, '%d', NOW(), %d, '')""" \
              % (url['id'], flag, downtime )
        self._db.executeSql(sql)
        sql = "update monitor_monitor set status='%s' where id=%d" % (flag, url['id'])
        return self._db.executeSql(sql)
            
                
    def checkCon(self):
        """检查连接，正常返回false, 不正常返回错误信息""" 
        ret = False
        url = self._url
        if url['data']['status'] == False:
            url['alert_num'] += 1            
            if url['alert_num'] > url['alert_count']:
                ret = "%s error!" % (url2str(url))
        return ret

    def checkNormal(self):
        """检查是否正常,正常返回true, 不正常返回false"""
        url = self._url
        if url['data']['status'] == False:
            ret = False
        else:
            ret = True
        return ret
    
    def check(self):
        url = self._url
        ret = self.checkNormal()
        #如果已经报警，现在正常了，报告正常，同时返回
        if ret and url['real_alert_num'] > 0:
            #step 1. send msg
            self.allAlert("%s ok" % url2str(url))
            #step 2, reset alert num
            url['real_alert_num'] = 0
            url['alert_num'] = 0 
            return True
        
        #检查连接是否正常，如果不正常，并且小于最大报警次数，开始报警
        msg = self.checkCon()
        if msg and url['real_alert_num'] < conf.MaxAlertTime:
            self.allAlert(msg)
            url['real_alert_num'] += 1
            #如果是第一次报警，记录当前时间
            if url['real_alert_num'] == 1:
                url['error_time'] = time.time()
            return True

    def allAlert(self, msg):
        url = self._url
        self._saveAlert()
        #if url['people']:
        #    self._doAlert(msg, url['people'])
        for p in url['people']:
            self._doAlert(msg, p)
        url['alert_num'] = 0
      

    def _doAlert(self, msg, recv):
        """mode为字符编码形式，第一位是邮件，第二位是短信，第三位是gtalk, 第四位是msn，1010表示邮件和gtalk"""
        if  recv['mode'][0] == '1' :
            sendMail(msg, recv['mail'])
            logging.error("[%s][%s]" % (recv['mail'], msg))
        
        if  recv['mode'][1] == '1' :
            sendMobile(msg, recv['mobile'])
            logging.error("[%s][%s]" % (recv['mobile'], msg))
        
        if  recv['mode'][2] == '1' :
            sendGtalk(msg, recv['gtalk'])
            logging.error("[%s][%s]" % (recv['gtalk'], msg))
 
        if  recv['mode'][3] == '1' :
            sendMsn(msg, recv['msn'])
            logging.error("[%s][%s]" % (recv['msn'], msg))
            
class Tpl:
    name = "Tpl"
    
    def __init__(self, url):
        print "Start %s..." % self.name
        self._url = url
        if not url.has_key('data'):
            self._url['data'] = {"total": 0, "now": 0, "status": True}

    def process(self):
        url = self._url
        try:
            if url['tpl'] == 'nginx':
                self._nginx()
            elif url['tpl'] == 'apache':
                self._apache()
            elif url['tpl'] == 'port':
                self._port()
            elif url['tpl'] == 'http':
                self._http()
            elif url['tpl'] == 'cmd':
                self._cmd()
        except:
            print url
            traceback.print_exc()
    

    def _nginx(self):
        url = self._url
        ret = getUrlData(url['url'])
        if ret['code'] != 200:
            url['data']['status'] = False
            total = url['data']['total']
            now = 0
        else:
            url['data']['status'] = True
            total = int (ret['data'].split()[9])
            if url['data']['total'] == 0:
                now = 0
            else:
                now = total - url['data']['total']
        url['data']['total'] = total
        url['data']['now'] = now
        logging.info("url: %s, status: %s" % (url['url'], url['data']['status'] ))
        return True
        
    def _apache(self):
        pass

    def _port(self):
        url = self._url
        (host, port) = url['url'].replace("tcp://", "").split(":")
        ret = detectPort(host, port)
        url['data']['status'] = ret
        url['data']['total'] = 0
        url['data']['now'] = int(ret)
        logging.info("url: %s, status: %s" % (url['url'], url['data']['status'] ))
        return 0

    def _http(self):
        url = self._url
        ret = detectHttp(url['url'])
        url['data']['status'] = ret
        url['data']['total'] = 0
        url['data']['now'] = int(ret)
        logging.info("url: %s, status: %s" % (url['url'], url['data']['status'] ))
        return 0

    def _cmd(self):
        url = self._url
        dct = {}
        cmd = "%s/%s" % (conf.bin, self._url['url'])
        fd = os.popen(cmd)
        try:
            for line in fd.readlines():
                (key, value) = line.strip("\n").split(":")
                dct[key.lower()] = value
            if dct['status'] == "1":
                url['data']['status'] = True
                url['data']['now'] = float(dct['value'])
            else:
                url['data']['status'] = False
                url['data']['now'] = 0
                
            url['data']['total'] = 0
        except:
            url['data']['status'] = False
            url['data']['total'] = 0
            url['data']['now'] = 0
        finally:
            fd.close()
        logging.info("url: %s, status: %s" % (url['url'], url['data']['status'] ))
        return 0
        
class Sender:
    name = "Sender"
    
    def __init__(self, db, url):
        print "Start %s..." % self.name
        self._url = url
        self._db = db
        self._tablename = url['table']
        if self._tablename:
            sql = """CREATE TABLE IF NOT EXISTS `%s` (
            `id` INT(11) NOT NULL AUTO_INCREMENT,
            `value` FLOAT NOT NULL,
            `timestamp` DATETIME NOT NULL,
            PRIMARY KEY (`id`),
            KEY `timestamp` (`timestamp`)
            ) ENGINE=MYISAM DEFAULT CHARSET=utf8 """ % self._tablename
            self._db.executeSql(sql)


    def process(self):
        if not self._tablename:
            return 0
        url = self._url
        #print "Time: %s, url: %s, Total: %s, Now: %s" % (time.ctime(), url['url'], url['data']['total'], url['data']['now'])
        sql = "insert into `%s`  (VALUE, TIMESTAMP) values(%f, now())" % (self._tablename, url['data']['now'])
        return self._db.executeSql(sql)
    

class MinHandler(BaseHTTPServer.BaseHTTPRequestHandler):

    def do_HEAD(self):
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.end_headers()
        
    def do_GET(self):
        """Respond to a GET request."""
        print self.path
        if self.path == "/status":
            msg = g_manage.status()
            self.send_response(200)
            self.send_header("Content-type", "text/html")
            self.end_headers()
            self.wfile.write(msg)
            return 
        
        match = re.findall("/([a-z]+)/(\d+)", self.path) 
        if not match:
            self.send_response(403)
            self.send_header("Content-type", "text/html")
            self.end_headers()
        else:
            (cmd, id) = match[0]
            id = int(id)
            if cmd == "reload":
                msg = self.reload(id)
            elif cmd == "start":
                msg = self.start(id)
            elif cmd == "stop":
                msg = self.stop(id)
            else:
                msg = "Error Command"
            self.send_response(200)
            self.send_header("Content-type", "text/html")
            self.end_headers()
            self.wfile.write(msg)
            return 
    
    def reload(self, id):
        ret = g_manage.setRefresh(id)
        if ret:
            flag = "True"
        else:
            flag = "False"
        return  "code:%s|Reload Thread %d  %s" %(flag, id, flag)
    
    def start(self, id):
        ret = g_manage.isExit(id)
        if ret:
            if g_manage.resume(id):
                return  "code:True|Start Thread %d Exist, Resume ok!" % id
            else:
                return  "code:True|Start Thread %d Exist, Resume Fails!" % id
        
        ret = g_manage.isExist(id)
        if ret:
            return  "code:False|Start Thread %d  Error, Thread exists!" % id
        if idExist(id) == False:
            return  "code:False|Start Thread %d  Error,  Open Status is False!" % id
        Collecter(g_dbw, g_dbr, id, g_manage)
        for i in xrange(0,3):
            ret = g_manage.isExist(id)
            if ret == True:
                break
            else:
                time.sleep(1)
        if ret:
            flag = "True"
        else:
            flag = "False"
        return  "code:%s|Start Thread %d  %s" %(flag,id, flag)
       
    def stop(self, id):
        ret = g_manage.setExit(id)
        if ret:
            flag = "True"
        else:
            flag = "False"
        return  "code:%s|Stop Thread %d  %s [%s]" %(flag, id, flag, time.time())
    

if __name__ == "__main__":
    print "start"
    threadList = []
    db = conf.db
    g_dbw = DbObject(db['host'], db['user'], db['passwd'], db['database'], db['port'])
    g_dbr = DbObject(db['host'], db['user'], db['passwd'], db['database'], db['port'], dict=True)
    #获取所有的监控id
    sql = "select id from monitor_monitor"
    urls = g_dbr.executeSqlRet(sql)
    #创建管理进程
    g_manage = Manage()   
    print "start urls, total number: %d" % len(urls)
    for elem in urls:
        id = elem['id']
        #if id == 273:
        Collecter(g_dbw, g_dbr, id, g_manage)
    (host, port) = conf.listen.split(':')
    httpd = BaseHTTPServer.HTTPServer((host, int(port)), MinHandler)
    print "start http server at %s"  % conf.listen
    httpd.serve_forever()
#    while True:
#        #str = raw_input()
#        #if str == 'quit':
#        #    break
#        time.sleep(10)
    
